export interface ITask {
  name: string;
  run: () => Promise<void>;
  extra: any;
}

export abstract class TaskChain {
  tasks: ITask[];

  abstract async enterTask(t: ITask): Promise<void>;

  abstract async leaveTask(t: ITask): Promise<void>;

  abstract async leaveTaskError(t: ITask, e: Error): Promise<void>;

  abstract async done(hasFailures: boolean): Promise<void>;

  async run() {
    const tasks = this.tasks;
    let hasFailures = false;
    for (let i = 0, len = tasks.length; i < len; i++) {
      const t = tasks[i];
      try {
        await this.enterTask(t);
        await t.run.apply(this);
        await this.leaveTask(t);
      } catch (e) {
        this.leaveTaskError(t, e);
        hasFailures = true;
        break;
      }
    }
    await this.done(hasFailures);
  }
}

export const task = (name: string) => {
  return (
    target: TaskChain,
    propertyKey: string,
    descriptor: TypedPropertyDescriptor<() => Promise<void>>
  ) => {
    let tasks = (target as any).tasks;
    if (!tasks) {
      tasks = [];
      (target as any).tasks = tasks;
    }
    const run = descriptor.value!;
    tasks.push({ name, run, extra: {} });
  };
};
